package com.wedimob.slideshare.video;

import java.io.InputStream;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;

import com.google.gson.Gson;

public class JavaYoutubeDownloader extends JavaVideoDownloader {

    private static final String scheme = "http";
    private static final String host = "www.youtube.com";
    private static final String URL_REGEX = "url=(.*?)(&|;|,|$)";
    private static final String SIGNED_URL_REGEX = "conn=(.*?)(&|;|,|$)";
    private static final String TAG_REGEX = "=(.+?)(,|;|:|&)";
    private static final String VIDEO_URL_REGEX = "(?<=watch\\?v=|/videos/)[^&#]*";

    private String videoId;

    public JavaYoutubeDownloader(String videoId, String outputDir) {
        super();
        this.videoId = videoId;
        if (videoId.contains("youtube.")) {
            Pattern compiledPattern = Pattern.compile(VIDEO_URL_REGEX);
            Matcher matcher = compiledPattern.matcher(videoId);

            if (matcher.find()) {
                this.videoId = matcher.group();
            }
            else {
                throw new RuntimeException(videoId + " is not a valid url");
            }
        }
        this.outputDir = outputDir;
    }

    @Override
    protected Map<String, String> getUrls() {
        try {
            if (info != null) {
                Map<String, String> result = new HashMap<String, String>();
                for (String quality : info.sizes.keySet()) {
                    result.put(quality, humanReadableByteCount(info.sizes.get(quality), false));
                }
                return result;
            }

            info = new VideoInfo();
            CookieStore cookieStore = new BasicCookieStore();
            info.encoding = "UTF-8";
            info.userAgent = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13";
            info.videoId = videoId;
            info.localContext = new BasicHttpContext();
            info.localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
            info.httpclient = new DefaultHttpClient();
            info.urls = new HashMap<String, String>();

            List<NameValuePair> qparams = new ArrayList<NameValuePair>();
            qparams.add(new BasicNameValuePair("v", videoId));
            @SuppressWarnings("deprecation")
            URI uri = URIUtils.createURI(scheme, host, -1, "/watch",
                    URLEncodedUtils.format(qparams, "UTF-8"), null);
            HttpGet httpget = new HttpGet(uri);
            httpget.setHeader("User-Agent", info.userAgent);
            HttpResponse response = info.httpclient.execute(httpget,
                    info.localContext);
            HttpEntity entity = response.getEntity();
            if (entity != null && response.getStatusLine().getStatusCode() == 200) {
                InputStream instream = entity.getContent();
                String videoInfo = getStringFromInputStream(info.encoding, instream);
                if (videoInfo != null && videoInfo.length() > 0) {
                    int beginIndex = videoInfo.indexOf("yt.playerConfig = ");
                    int endIndex = videoInfo.indexOf("};", beginIndex + 18);
                    String json = videoInfo
                            .substring(beginIndex + 18, endIndex + 1);
                    Gson gson = new Gson();
                    JSONResponse jsonObj = gson.fromJson(json, JSONResponse.class);

                    info.title = jsonObj.args.title;

                    String urlMap = URLDecoder.decode(
                            jsonObj.args.url_encoded_fmt_stream_map, "UTF-8");
                    Pattern pattern = Pattern.compile(URL_REGEX,
                            Pattern.CASE_INSENSITIVE);

                    Pattern typePattern = Pattern.compile("type" + TAG_REGEX,
                            Pattern.CASE_INSENSITIVE);
                    Pattern qualityPattern = Pattern.compile("quality" + TAG_REGEX,
                            Pattern.CASE_INSENSITIVE);
                    Pattern sigPattern = Pattern.compile("sig" + TAG_REGEX,
                            Pattern.CASE_INSENSITIVE);

                    Matcher matcher = pattern
                            .matcher(jsonObj.args.url_encoded_fmt_stream_map);
                    Matcher typematcher = typePattern.matcher(urlMap);
                    Matcher qualitymatcher = qualityPattern.matcher(urlMap);
                    Matcher sigmatcher = sigPattern.matcher(urlMap);
                    // calculate the number of matches
                    int numOfMatches = 0;
                    while (matcher.find()) {
                        numOfMatches++;
                    }
                    matcher.reset();

                    if (numOfMatches == 0) {
                        pattern = Pattern.compile(SIGNED_URL_REGEX,
                                Pattern.CASE_INSENSITIVE);
                        matcher = pattern
                                .matcher(jsonObj.args.url_encoded_fmt_stream_map);
                        while (matcher.find()) {
                            numOfMatches++;
                        }
                        matcher.reset();
                    }

                    if (numOfMatches == 0)
                        return null;

                    if (!sigmatcher.find()) {
                        sigPattern = Pattern.compile("signature" + TAG_REGEX,
                                Pattern.CASE_INSENSITIVE);
                        sigmatcher = sigPattern.matcher(urlMap);
                    } else {
                        sigmatcher.reset();
                    }

                    String[][] links = new String[4][numOfMatches];
                    int i = 0;
                    while (matcher.find() && typematcher.find()
                            && qualitymatcher.find() && sigmatcher.find()) {

                        links[0][i] = matcher.group(1);
                        links[1][i] = typematcher.group(1);
                        links[2][i] = qualitymatcher.group(1);
                        links[3][i] = sigmatcher.group(1);

                        i++;
                        info.urls.put(links[1][i - 1] + ":" + links[2][i - 1],
                                URLDecoder.decode(links[0][i - 1], "UTF-8")
                                        + "&newshard=yes&signature="
                                        + links[3][i - 1]);
                    }
                }
            }

            info.sizes = new HashMap<String, Integer>();
            for (String quality : info.urls.keySet()) {
                String url = info.urls.get(quality);
                info.sizes.put(quality, getFileSize(url));
            }

            Map<String, String> result = new HashMap<String, String>();
            for (String quality : info.sizes.keySet()) {
                result.put(quality, humanReadableByteCount(info.sizes.get(quality), true));
            }

            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static class JSONArgsResponse {
        public String url_encoded_fmt_stream_map;
        public String title;
    }

    private static class JSONResponse {
        public JSONArgsResponse args;
    }
}